// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com

// ReSharper disable CheckNamespace
// ReSharper disable ClassNeverInstantiated.Global
// ReSharper disable CommentTypo
// ReSharper disable IdentifierTypo
// ReSharper disable InconsistentNaming
// ReSharper disable StringLiteralTypo
// ReSharper disable UnusedParameter.Local

/*
 * Ars Magna project, http://arsmagna.ru
 */

#region Using directives

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Net;

#endregion

#nullable enable

namespace AM.Reporting.Utils
{
    internal static class ImageHelper
    {
        public static Bitmap CloneBitmap (Image source)
        {
            if (source == null)
            {
                return null;
            }

            var image = new Bitmap (source.Width, source.Height);
            image.SetResolution (source.HorizontalResolution, source.VerticalResolution);
            using (var g = Graphics.FromImage (image))
            {
                g.DrawImageUnscaled (source, 0, 0);
            }

            return image;

            // this can throw OutOfMemory when creating a grayscale image from a cloned bitmap
            //      return source.Clone() as Bitmap;
        }

        public static void Save (Image image, Stream stream)
        {
            Save (image, stream, image.GetImageFormat());
        }

        public static void Save (Image image, string fileName, ImageFormat format)
        {
            using (var stream = new FileStream (fileName, FileMode.Create))
            {
                Save (image, stream, format);
            }
        }

        public static void Save (Image image, Stream stream, ImageFormat format)
        {
            if (image == null)
            {
                return;
            }

            if (image is Bitmap)
            {
                if (format == ImageFormat.Icon)
                {
                    SaveAsIcon (image, stream, true);
                }
                else
                {
                    image.Save (stream, format);
                }
            }
            else if (image is Metafile)
            {
                Metafile emf = null;
                using (var bmp = new Bitmap (1, 1))
                using (var g = Graphics.FromImage (bmp))
                {
                    var hdc = g.GetHdc();
                    emf = new Metafile (stream, hdc);
                    g.ReleaseHdc (hdc);
                }

                using (var g = Graphics.FromImage (emf))
                {
                    g.DrawImage (image, 0, 0);
                }
            }
        }

        public static bool SaveAndConvert (Image image, Stream stream, ImageFormat format)
        {
            if (image == null)
            {
                return false;
            }

            if (format == ImageFormat.Jpeg || format == ImageFormat.Gif
                                           || format == ImageFormat.Tiff || format == ImageFormat.Bmp
                                           || format == ImageFormat.Png
                                           || format == ImageFormat.MemoryBmp)
            {
                if (image is Bitmap)
                {
                    if (format == ImageFormat.MemoryBmp)
                    {
                        throw new Exception (Res.Get ("Export,Image,ImageParceFormatException"));
                    }

                    image.Save (stream, format);
                    return true;
                }

                //from mf to bitmap
                using (var metafile = image as Metafile)
                using (var bitmap = new Bitmap (image.Width, image.Height))
                {
                    bitmap.SetResolution (96F, 96F);
                    using (var g = Graphics.FromImage (bitmap))
                    {
                        g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
                        g.DrawImage (metafile, 0, 0, (float)image.Width, (float)image.Height);
                        g.Dispose();
                    }

                    bitmap.Save (stream, format);
                }

                return true;
            }
            else if (format == ImageFormat.Icon)
            {
                return SaveAsIcon (image, stream, true);
            }
            else if (format == ImageFormat.Wmf || format == ImageFormat.Emf)
            {
                if (image is Metafile)
                {
                    Metafile emf = null;
                    using (var bmp = new Bitmap (1, 1))
                    using (var g = Graphics.FromImage (bmp))
                    {
                        var hdc = g.GetHdc();
                        emf = new Metafile (stream, hdc);
                        g.ReleaseHdc (hdc);
                    }

                    using (var g = Graphics.FromImage (emf))
                    {
                        g.DrawImage (image, 0, 0);
                    }

                    return true;
                }
            }

            //throw new Exception(Res.Get("Export,Image,ImageParceFormatException")); // we cant convert image to exif or from bitmap to mf
            return false;
        }

        public static byte[] Load (string fileName)
        {
            if (!string.IsNullOrEmpty (fileName))
            {
                return File.ReadAllBytes (fileName);
            }

            return null;
        }

        public static Image Load (byte[] bytes)
        {
            if (bytes is { Length: > 0 })
            {
                try
                {
#if CROSSPLATFORM
                    // TODO memory leaks image converter
                    return Image.FromStream(new MemoryStream(bytes));
#else
                    return new ImageConverter().ConvertFrom (bytes) as Image;
#endif
                }
                catch
                {
                    var errorBmp = new Bitmap (10, 10);
                    using (var g = Graphics.FromImage (errorBmp))
                    {
                        g.DrawLine (Pens.Red, 0, 0, 10, 10);
                        g.DrawLine (Pens.Red, 0, 10, 10, 0);
                    }

                    return errorBmp;
                }
            }

            return null;
        }

        public static byte[] LoadURL (string url)
        {
            if (!string.IsNullOrEmpty (url))
            {
                ServicePointManager.SecurityProtocol = (SecurityProtocolType)(0xc0 | 0x300 | 0xc00);
                using (var web = new WebClient())
                {
                    return web.DownloadData (url);
                }
            }

            return null;
        }

        public static Bitmap GetTransparentBitmap (Image source, float transparency)
        {
            if (source == null)
            {
                return null;
            }

            var colorMatrix = new ColorMatrix
            {
                Matrix33 = 1 - transparency
            };
            var imageAttributes = new ImageAttributes();
            imageAttributes.SetColorMatrix (
                colorMatrix,
                ColorMatrixFlag.Default,
                ColorAdjustType.Bitmap);

            var width = source.Width;
            var height = source.Height;
            var image = new Bitmap (width, height);
            image.SetResolution (source.HorizontalResolution, source.VerticalResolution);

            using (var g = Graphics.FromImage (image))
            {
                g.Clear (Color.Transparent);
                g.DrawImage (
                    source,
                    new Rectangle (0, 0, width, height),
                    0, 0, width, height,
                    GraphicsUnit.Pixel,
                    imageAttributes);
            }

            return image;
        }

        public static Bitmap GetGrayscaleBitmap (Image source)
        {
            var grayscaleBitmap = new Bitmap (source.Width, source.Height, source.PixelFormat);

            // Red should be converted to (R*.299)+(G*.587)+(B*.114)
            // Green should be converted to (R*.299)+(G*.587)+(B*.114)
            // Blue should be converted to (R*.299)+(G*.587)+(B*.114)
            // Alpha should stay the same.
            var grayscaleMatrix = new ColorMatrix (new float[][]
            {
                new float[] { 0.299f, 0.299f, 0.299f, 0, 0 },
                new float[] { 0.587f, 0.587f, 0.587f, 0, 0 },
                new float[] { 0.114f, 0.114f, 0.114f, 0, 0 },
                new float[] { 0, 0, 0, 1, 0 },
                new float[] { 0, 0, 0, 0, 1 }
            });

            var attributes = new ImageAttributes();
            attributes.SetColorMatrix (grayscaleMatrix);

            // Use a Graphics object from the new image
            using (var graphics = Graphics.FromImage (grayscaleBitmap))
            {
                // Draw the original image using the ImageAttributes we created
                graphics.DrawImage (source,
                    new Rectangle (0, 0, grayscaleBitmap.Width, grayscaleBitmap.Height),
                    0, 0, grayscaleBitmap.Width, grayscaleBitmap.Height,
                    GraphicsUnit.Pixel, attributes);
            }

            return grayscaleBitmap;
        }

        /// <summary>
        /// Converts a PNG image to a icon (ico)
        /// </summary>
        /// <param name="image">The input image</param>
        /// <param name="output">The output stream</param>
        /// <param name="preserveAspectRatio">Preserve the aspect ratio</param>
        /// <returns>Wether or not the icon was succesfully generated</returns>
        public static bool SaveAsIcon (Image image, Stream output, bool preserveAspectRatio = false)
        {
            var size = 256;
            float width = size, height = size;
            if (preserveAspectRatio)
            {
                if (image.Width > image.Height)
                {
                    height = ((float)image.Height / image.Width) * size;
                }
                else
                {
                    width = ((float)image.Width / image.Height) * size;
                }
            }

            var newBitmap = new Bitmap (image, new Size ((int)width, (int)height));
            if (newBitmap == null)
            {
                return false;
            }

            // save the resized png into a memory stream for future use
            using (var memoryStream = new MemoryStream())
            {
                newBitmap.Save (memoryStream, ImageFormat.Png);

                var iconWriter = new BinaryWriter (output);
                if (output == null || iconWriter == null)
                {
                    return false;
                }

                // 0-1 reserved, 0
                iconWriter.Write ((byte)0);
                iconWriter.Write ((byte)0);

                // 2-3 image type, 1 = icon, 2 = cursor
                iconWriter.Write ((short)1);

                // 4-5 number of images
                iconWriter.Write ((short)1);

                // image entry 1
                // 0 image width
                iconWriter.Write ((byte)width);

                // 1 image height
                iconWriter.Write ((byte)height);

                // 2 number of colors
                iconWriter.Write ((byte)0);

                // 3 reserved
                iconWriter.Write ((byte)0);

                // 4-5 color planes
                iconWriter.Write ((short)0);

                // 6-7 bits per pixel
                iconWriter.Write ((short)32);

                // 8-11 size of image data
                iconWriter.Write ((int)memoryStream.Length);

                // 12-15 offset of image data
                iconWriter.Write ((int)(6 + 16));

                // write image data
                // png data must contain the whole png data file
                iconWriter.Write (memoryStream.ToArray());

                iconWriter.Flush();
            }

            return true;
        }
    }

    public static class ImageExtension
    {
        /// <summary>
        /// Returns an Image format.
        /// </summary>
        public static ImageFormat GetImageFormat (this Image bitmap)
        {
            if (bitmap == null || bitmap.RawFormat == null)
            {
                return null;
            }

            ImageFormat format = null;
            if (ImageFormat.Jpeg.Equals (bitmap.RawFormat))
            {
                format = ImageFormat.Jpeg;
            }
            else if (ImageFormat.Gif.Equals (bitmap.RawFormat))
            {
                format = ImageFormat.Gif;
            }
            else if (ImageFormat.Png.Equals (bitmap.RawFormat))
            {
                format = ImageFormat.Png;
            }
            else if (ImageFormat.Emf.Equals (bitmap.RawFormat))
            {
                format = ImageFormat.Emf;
            }
            else if (ImageFormat.Icon.Equals (bitmap.RawFormat))
            {
                format = ImageFormat.Icon;
            }
            else if (ImageFormat.Tiff.Equals (bitmap.RawFormat))
            {
                format = ImageFormat.Tiff;
            }
            else if (ImageFormat.Bmp.Equals (bitmap.RawFormat) ||
                     ImageFormat.MemoryBmp.Equals (bitmap.RawFormat)) // MemoryBmp format raises a GDI exception
            {
                format = ImageFormat.Bmp;
            }
            else if (ImageFormat.Wmf.Equals (bitmap.RawFormat))
            {
                format = ImageFormat.Wmf;
            }

            if (format != null)
            {
                return format;
            }

            return ImageFormat.Bmp;
        }
    }
}
